import { consts } from "@/module-00-codebase/pkg-00-const";
import { Bi } from "@/module-00-codebase/pkg-01-container";
import { aw, PendingPromise, pendingResolve } from "@/module-00-codebase/pkg-02-tool";
import { RpcResult } from "@/module-00-codebase/pkg-08-entity/rpc-result";

import axios, { AxiosRequestConfig } from "axios";
// ---------- http rpc ----------------
type HttpRequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
const defaultParams: AxiosRequestConfig = {
  method: "POST",
  headers: { "content-type": "application/json;charset=utf-8" }
  //   onDownloadProgress: function (event) {
  //     console.log(event);
  //   }
};
const BATCH_DEFAULT = { action: "batch", resourceName: "any" };
class HttpRpc {
  public async send(params: any) {
    try {
      // 1. assign default params
      const options: any = { ...defaultParams };
      // 2. set url
      if (params.url.indexOf("http") === 0) {
        options.url = params.url;
      } else {
        options.url = consts.HTTP_BASE_PATH + params.url;
      }
      // 3. set method
      if (params.method !== undefined) {
        options.method = params.method;
      }
      // 4. set data
      if (params.data !== undefined) {
        options.data = Bi.dataExchange.serialize(Bi.utils.deepCopy(params.data, (obj: any, property: string, value: any) => !("id" === property && typeof value === "string" && value.indexOf("tempid_") === 0)));
      } else if (params.method !== "GET" && params.method !== "DELETE") {
        options.data = "{}";
      }
      // 5. if loading
      if (params.loading !== false) {
        Bi.loading.on(params.loading);
      }
      // 7. add token to headers
      if (!(options.headers.token = Bi.cacheManager.read("token"))) {
        delete options.headers.token;
      }
      // 8. create http request
      return (await axios.request(options)).data;
    } catch (ex: unknown) {
      const e = ex instanceof Error ? ex : new Error(JSON.stringify(ex));
      throw e;
    } finally {
      if (params.loading !== false) Bi.loading.off(200, params.loading);
    }
  }
}
// ------- rest rpc ------------------------------
export type Action = "getone" | "getlist" | "post" | "put" | "patch" | "delete" | "batch";
const action_method_map: { [key: string]: HttpRequestMethod } = {
  getone: "GET",
  getlist: "POST",
  post: "POST",
  put: "PUT",
  patch: "PATCH",
  delete: "DELETE",
  batch: "POST",
  exists: "POST"
};
const idActionSet: Set<Action> = new Set<Action>(["getone", "delete", "put", "patch"]);
const bdMethodSet: Set<HttpRequestMethod> = new Set<HttpRequestMethod>(["POST", "PUT", "PATCH"]);
class OptsCbk {
  constructor(public params: any, public promise: PendingPromise<any>) {}
}
const rpcBatchCache: OptsCbk[] = [];
export class RestRpc extends HttpRpc {
  public async send(params: any) {
    params.data = params.data || {};
    const pending = pendingResolve<RpcResult>();
    if (
      params.resourceName !== "innerUser" && //e
      !params.disableBatch && //e
      !!params.resourceName && //e
      !!Bi.cacheManager.read("token") && //e
      params.action !== "batch" && //e
      !!Bi.propertyDescription.get(Bi.utils.upperFirst(params.resourceName)) //e
    ) {
      rpcBatchCache.push(new OptsCbk(params, pending));
      await aw(0);
      const cache = rpcBatchCache.splice(0, rpcBatchCache.length);
      if (cache.length === 0) {
        // skip
      } else if (cache.length === 1) {
        const res = await Bi.rpc.send({ ...cache[0].params, disableBatch: true });
        pending.resolve(res);
      } else {
        const loadings = cache2loading(cache).filter(l => l !== false);
        loadings.forEach(l => Bi.loading.on.apply(null, [l].filter(Boolean) as any));
        const res = await Bi.rpc.send(cache2params(cache));
        for (let i = 0; i < cache.length; i++) {
          let readResult: RpcResult;
          if (!!res.result) {
            readResult = res.data.actions[i].res;
          } else {
            readResult = new RpcResult(res.message);
          }
          const handledRes = await Bi.rpcHandle.success(params, readResult);
          cache[i].promise.resolve(handledRes);
        }
        loadings.forEach(() => Bi.loading.off());
      }
    } else {
      const options: any = { loading: params.loading };
      // 1. set method
      options.method = params.method || action_method_map[params.action];
      // 2. set url
      if (params.resourceName && params.action) {
        options.url = "/resource/" + params.resourceName + "/" + params.action;
        // 3. has id in url path.
        if (idActionSet.has(params.action)) {
          if ((params.data as Object)?.hasOwnProperty("id")) {
            options.url = "/resource/" + params.resourceName + "/" + params.action + "/" + (params.data.id || "");
          } else {
            options.url = "/resource/" + params.resourceName + "/" + params.action + "/";
          }
        }
      } else if (params.url) {
        options.url = params.url;
      }
      // 4. has data in body
      if (bdMethodSet.has((options.method as string).toUpperCase() as any)) {
        options.data = { ...params.data };
      }
      const res = await super.send(options);
      const handledRes = await Bi.rpcHandle.success(params, res);
      pending.resolve(handledRes);
    }
    return pending;
  }
}
export const dependencies = () => ["Loading", "RpcHandle"];
const cache2params = (cache: OptsCbk[]) => ({ ...BATCH_DEFAULT, data: { actions: cache.map(oc => ({ method: oc.params.action, params: oc.params.data, resourceName: oc.params.resourceName })) }, loading: false });
const cache2loading = (cache: OptsCbk[]) => cache.map(c => c.params.loading);
export const mergeSend = async () => {};
